<?php
/**
 * 模板
 *
 */
class Template {
	private $_sTplPath;
	private $_sTmpPath;
	private $_sTplFile;
	private $_sTmpFile;
	private $_sAliasFile;
	private $_aVars = array ();
	private $_aAlias = array ();
	private $_aIncludeFiles = array ();
	private $_aClass = array ();
	
	/**
	 * 构造函数
	 *
	 * @param string $sFile
	 */
	public function __construct($sFile = null) {
		self::setTplPath ();
		self::setTmpPath ();
		if ($sFile) {
			self::open ( $sFile );
		}
	}
	
	/**
	 * 设置模板文件所在目录如果不设置可以使用文件绝对路径
	 *
	 * @param string $sPath
	 */
	public function setTplPath($sPath = null) {
		if ($sPath == null) {
			$sConfPath = Base::getConf ( 'template.tpl_path' );
		} else {
			$sConfPath = $sPath;
		}
		$sRealPath = realpath ( $sConfPath );
		if (! $sRealPath) {
			$sRealPath = realpath ( LIB . '/' . $sConfPath );
		}
		try {
			if (! $sRealPath && $sPath != null) {
				throw new ExceptionExt ( '模板目录' . $sPath . '不存在' );
			}
		} catch ( ExceptionExt $e ) {
			$e->showError ();
		}
		$this->_sTplPath = $sRealPath;
	}
	
	/**
	 * 设置编译后模板存放路径
	 *
	 * @param string $sPath
	 */
	public function setTmpPath($sPath = null) {
		try {
			if ($sPath == null) {
				$sPath = Base::getConf ( 'template.tmp_path' );
				if ($sPath == null) {
					if ($_ENV ["TEMP"]) {
						$sRealPath = $_ENV ["TEMP"];
					} else {
						$sRealPath = '/tmp';
					}
				} else {
					$sRealPath = realpath ( $sPath );
					if (! $sRealPath) {
						$sRealPath = realpath ( LIB . '/' . $sPath );
					}
					
					if (! $sRealPath && $sPath != null) {
						throw new ExceptionExt ( '模板生成临时目录' . $sPath . '不存在' );
					}
				}
			}
			if (! is_writable ( $sRealPath ) && $sPath != null) {
				throw new ExceptionExt ( '模板生成临时目录' . $sRealPath . '不可写' );
			}
		} catch ( ExceptionExt $e ) {
			$e->showError ();
		}
		$this->_sTmpPath = $sRealPath;
	}
	
	/**
	 * 给变量赋值
	 *
	 * @param string $sName
	 * @param string $sVal
	 */
	public function assign($sName, $sVal) {
		$this->_aVars [$sName] = $sVal;
	}
	
	/**
	 * 打开一个模板文件
	 *
	 * @param string $sFile
	 */
	public function open($sFile) {
		if ($this->_sTplPath == null) {
			self::setTplPath ();
		}
		if ($this->_sTmpPath == null) {
			self::setTmpPath ();
		}
		$this->_sTplFile = $this->_sTplPath . '/' . $sFile;
		$this->_sTmpFile = $this->_sTmpPath . '/' . $sFile . '.php';
		$rFp = @fopen ( $this->_sTmpFile, 'r' );
		if ($rFp) {
			$aMatch = array ();
			preg_match ( '/(\d+)/', fgets ( $rFp, 100 ), $aMatch );
			if ($aMatch [0] < @filemtime ( $this->_sTplFile )) {
				$sTpl = '<?php /*' . @filemtime ( $this->_sTplFile ) . '*/?>' . "\n" . self::_parse ( $this->_sTplFile );
				self::_writeTpl ( $sTpl, $this->_sTmpFile );
				self::_getInclude ( $sTpl );
			}
		} else {
			$sTpl = '<?php /*' . @filemtime ( $this->_sTplFile ) . '*/?>' . "\n" . self::_parse ( $this->_sTplFile );
			self::_writeTpl ( $sTpl, $this->_sTmpFile );
			self::_getInclude ( $sTpl );
		}
	}
	
	/**
	 * 获取一个编译后模板文件的内容
	 *
	 * @param string $sFile
	 * @return string
	 */
	public function get($sFile = null) {
		if ($sFile != null) {
			self::open ( $sFile );
		}
		return file_get_contents ( $this->_sTmpPath . '/' . $sFile . '.php' );
	}
	
	/**
	 * 输出显示模板
	 *
	 * @param string $sFile
	 */
	public function output($sFile = null) {
		if ($sFile != null) {
			self::open ( $sFile );
		}
		if (count ( $this->_aVars )) {
			foreach ( $this->_aVars as $sKey => $sRow ) {
				eval ( 'global $' . $sKey . ';' );
				eval ( '$' . $sKey . ' = ' . var_export ( $sRow, true ) . ';' );
			}
		}
		
		include $this->_sTmpPath . '/' . $sFile . '.php';
	}
	
	private function _parse($sFile) {
		try {
			$sTpl = @file_get_contents ( $sFile );
			if ($sTpl == false) {
				throw new ExceptionExt ( '模板文件' . $sFile . '不存在' );
			}
			$sTpl = preg_replace ( '/\{(\$[a-zA-Z0-9_\[\]\'\"\$\.\x7f-\xff]+)\}/s', '<?php echo \\1;?>', $sTpl );
			$sTpl = preg_replace ( '/[\n\r\t]*\{elseif\s+(.+?)\}[\n\r\t]*/is', '<?php } elseif (\\1) {?>', $sTpl );
			$sTpl = preg_replace ( '/[\n\r\t]*\{else\}[\n\r\t]*/is', '<?php } else {?>', $sTpl );
			$sTpl = preg_replace ( '/[\n\r\t]*\{echo\s+(.+?)\}[\n\r\t]*/is', '<?php echo \\1;?>', $sTpl );
			$sTpl = preg_replace ( '/[\n\r\t]*\{include\s+(\S+)\}[\n\r\t]*/is', '<?php include \'\\1' . '.php\';?>', $sTpl );
			$sTpl = preg_replace_callback ( '/[\n\r\t]*\{(\w+)\:(.*?)\}[\n\r\t]*/is', array (&$this, '_getFunction' ), $sTpl );
			
			for($i = 0; $i < 3; $i ++) {
				$sTpl = preg_replace ( '/[\n\r\t]*\{foreach\s+(\S+)\s+(\S+)\}[\n\r]*(.+?)[\n\r]*\{\/foreach\}/s', '<?php if(is_array(\\1)){foreach(\\1 as \\2){?>' . "\n" . '\\3' . "\n" . '<?php }}?>' . "\n", $sTpl );
				$sTpl = preg_replace ( '/[\n\r\t]*\{foreach\s+(\S+)\s+(\S+)\s+(\S+)\}[\n\r]*(.+?)[\n\r]*\{\/foreach\}/is', '<?php if(is_array(\\1)){foreach(\\1 as \\2 => \\3){?>' . "\n" . '\\4' . "\n" . '<?php }}?>' . "\n", $sTpl );
				$sTpl = preg_replace ( '/[\n\r\t]*\{if\s+(.+?)\}[\n\r]*(.+?)[\n\r]*\{\/if\}/is', '<?php if(\\1){?>' . '\\2' . '<?php }?>', $sTpl );
			}
			
			if (count ( $this->_aClass )) {
				$sTpl = '<?php' . "\n" . join ( "\n", $this->_aClass ) . "\n" . '?>' . "\n" . $sTpl;
			}
			if (count ( $this->_aIncludeFiles )) {
				$sTpl = '<?php' . "\n" . join ( "\n", $this->_aIncludeFiles ) . "\n" . '?>' . "\n" . $sTpl;
			}
			$sTpl = $sGlobal . $sTpl;
		} catch ( ExceptionExt $e ) {
			$e->showError ();
		}
		
		return $sTpl;
	}
	
	private function _writeTpl($sTpl, $sFile) {
		file_put_contents ( $sFile, $sTpl );
	}
	
	public function _getInclude($sTpl) {
		$aMatch = array ();
		$aTplFiles = array ();
		preg_match_all ( '/<\?php include (\S+);\?>/', $sTpl, $aMatch );
		if (count ( $aMatch [1] )) {
			$aMatch [1] = array_unique ( $aMatch [1] );
			foreach ( $aMatch [1] as $sRow ) {
				eval ( '$aTplFiles[] = ' . $sRow . ';' );
			}
			$aTplFiles = array_unique ( $aTplFiles );
			foreach ( $aTplFiles as $sRow ) {
				self::open ( substr ( $sRow, 0, - 4 ) );
			}
		}
	}
	
	private function _getGlobal($sTpl) {
		$aMatch = array ();
		$aVarName = array ();
		$_aGlobal = array ('$_GET', '$_POST', '$_FILE', '$_SERVER', '$_SESSION' );
		preg_match_all ( '/\$[a-zA-Z0-9_\[\]\'\"\$\.\x7f-\xff]+/', $sTpl, $aMatch );
		$aMatch = array_unique ( $aMatch );
		if (count ( $aMatch [0] )) {
			foreach ( $aMatch [0] as $sRow ) {
				list ( $sVarName ) = explode ( '[', $sRow );
				if (! array_key_exists ( substr ( $sVarName, 1 ), $this->_aVars ) && ! in_array ( $sVarName, $_aGlobal )) {
					$aVarName [] = $sVarName;
				}
			}
			if (count ( $aVarName )) {
				$aVarName = array_unique ( $aVarName );
				$sGlobal = '<?php global ' . join ( ',', $aVarName ) . ';?>' . "\n";
			}
		}
		
		return $sGlobal;
	}
	
	private function _getFunction($aMatch) {
		if (! is_array ( $this->_aAlias )) {
			@$this->_aAlias = parse_ini_file ( ROOT . '/include/tplalias.php' );
		}
		
		if (array_key_exists ( $aMatch [1], $this->_aAlias )) {
			list ( $sType, $sFile, $sFunction ) = explode ( ' ', $this->_aAlias [$aMatch [1]] );
			
			if ($sType == 'class') {
				if (strrpos ( $sFile, '_' ) === false) {
					$sClass = $sFile;
				} else {
					$sClass = substr ( $sFile, strrpos ( $sFile, '_' ) + 1 );
				}
				
				eval ( 'global $o' . $sClass . ';' );
				if (! is_object ( 'o' . $sClass )) {
					if (! array_key_exists ( $sClass, $this->_aClass )) {
						$this->_aClass [$sClass] = '$o' . $sClass . ' = new ' . $sFile . '();';
					}
					return '<?php $o' . $sClass . '->' . $sFunction . '(' . $aMatch [2] . ');?>';
				}
			} elseif ($sType === 'func') {
				if (! array_key_exists ( $sFile, $this->_aIncludeFiles )) {
					$this->_aIncludeFiles [$sFile] = 'include_once ROOT.\'/' . $sFile . '\';';
				}
				
				return '<?php ' . $sFunction . '(' . $aMatch [2] . ');?>';
			}
		}
		
		return '';
	}
}